package com.firexxx.common.web;

import com.firexxx.common.errors.BaseErrors;
import com.firexxx.common.errors.BusinessError;
import com.firexxx.common.errors.BusinessException;
import com.firexxx.common.errors.ErrorType;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.validation.ConstraintViolationException;
import java.util.Objects;
import java.util.Optional;

/**
 * Created by mmxu6 on 2019-09-03.
 */
@Slf4j
@RestControllerAdvice(annotations = RestController.class)
public class RestExceptionHandler {
    private final MessageSource messageSource;

    public RestExceptionHandler(MessageSource messageSource) {
        this.messageSource = messageSource;
    }

    @ExceptionHandler(Throwable.class)
    public RestResponse<?> unknownThrowable(Throwable ex) {
        String message = getMessageByThrowable(ex);

        BusinessError error = BaseErrors.UNKNOWN_ERROR.message(message);
        if (StringUtils.hasText(message)) {
            log.warn(error.getLoggerMessage());
        } else {
            log.error(error.getLoggerMessage(), ex);
        }

        return RestResponse.error(error);
    }

    @ExceptionHandler(BusinessException.class)
    public RestResponse<?> businessException(BusinessException ex) {
        if (ex.getError().getErrorType() == ErrorType.ADAPTER) {
            log.warn(ex.getMessage());
        } else if (ex.getError().getErrorType() == ErrorType.BUSINESS) {
            log.error(ex.getLocalizedMessage(), ex);
        }

        return RestResponse.error(ex.getError());
    }

    /**
     * 校验异常
     */
    @ExceptionHandler(value = {ConstraintViolationException.class, ServletRequestBindingException.class})
    public RestResponse<?> mixBindException(Exception ex) {
        BusinessError error = BaseErrors.INVALID_PARAM;
        error.message(ex.getLocalizedMessage());

        log.warn(error.getLoggerMessage(), ex);
        return RestResponse.error(error);
    }

    @ExceptionHandler(BindException.class)
    public RestResponse<?> bindException(BindException ex) {
        BusinessError error = BaseErrors.INVALID_PARAM;
        Optional.ofNullable(ex.getFieldError()).map(FieldError::toString).ifPresent(error::message);
        log.error(error.getLoggerMessage(), ex);
        return RestResponse.error(error);
    }

    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    public RestResponse<?> httpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException ex) {
        BusinessError error = BaseErrors.INVALID_PARAM;
        log.error(error.getLoggerMessage(), ex);
        return RestResponse.error(error);
    }

    @ExceptionHandler(HttpMessageNotReadableException.class)
    public RestResponse<?> httpMessageNotReadableException(HttpMessageNotReadableException ex) {
        BusinessError error = BaseErrors.INVALID_PARAM;
        log.error(error.getLoggerMessage(), ex);
        return RestResponse.error(error);
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public RestResponse<?> methodArgumentNotValidException(MethodArgumentNotValidException ex) {
        BusinessError error = BaseErrors.INVALID_PARAM;
        log.error(error.getLoggerMessage(), ex);
        return RestResponse.error(error);
    }

    @ExceptionHandler(MissingServletRequestParameterException.class)
    public RestResponse<?> missingServletRequestParameterException(MissingServletRequestParameterException ex) {
        BusinessError error = BaseErrors.INVALID_PARAM;
        log.error(error.getLoggerMessage(), ex);
        return RestResponse.error(error);
    }

    private String getMessageByThrowable(Throwable ex) {
        Throwable t = ex;
        String message = null;
        while (Objects.nonNull(t) && Objects.isNull(message = getMessage(t.getMessage()))) {
            t = t.getCause();
        }
        return message;
    }

    private String getMessage(String code, Object... args) {
        if (StringUtils.isEmpty(code)) {
            return code;
        }
        return messageSource.getMessage(code, args, null, LocaleContextHolder.getLocale());
    }
}
